package org.execute.tool.adsPower;

import org.openqa.selenium.*;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.time.Duration;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 网页自动化操作类
 * 封装常见的网页交互操作，基于Selenium WebDriver
 */
public class AdsPowerWebAction { // 统一类名大小写
    private  WebDriver driver;
    private WebDriverWait wait;
    private  Actions actions;
    private int timeoutSeconds;

    // 默认等待时间（秒）
    private static final int DEFAULT_TIMEOUT = 10;
    // 默认截图目录
    private static final String DEFAULT_SCREENSHOT_DIR = "screenshots";

    /**
     * 构造网页自动化处理器
     *
     * @param driver WebDriver实例（非空）
     * @throws IllegalArgumentException 当driver为null时抛出
     */
    public AdsPowerWebAction(WebDriver driver) {
        this(driver, DEFAULT_TIMEOUT);
    }

    /**
     * 构造网页自动化处理器，指定自定义超时时间
     *
     * @param driver         WebDriver实例（非空）
     * @param timeoutSeconds 超时时间（秒，必须>0）
     * @throws IllegalArgumentException 当driver为null或超时时间无效时抛出
     */
    public AdsPowerWebAction(WebDriver driver, int timeoutSeconds) {
        if (driver == null) {
            throw new IllegalArgumentException("WebDriver实例不能为空");
        }
        if (timeoutSeconds <= 0) {
            throw new IllegalArgumentException("超时时间必须大于0");
        }
        this.driver = driver;
        this.timeoutSeconds = timeoutSeconds;
        this.wait = new WebDriverWait(driver, (long) timeoutSeconds * 1000);
        this.actions = new Actions(driver);
    }

    // ======================== 页面导航操作 ========================

    /**
     * 导航到指定URL
     *
     * @param url 目标URL（非空）
     * @throws IllegalArgumentException 当url为null/空时抛出
     */
    public void navigateTo(String url) {
        if (url == null || url.trim().isEmpty()) {
            throw new IllegalArgumentException("URL不能为空");
        }
        driver.get(url);
    }

    /**
     * 刷新当前页面
     */
    public void refreshPage() {
        driver.navigate().refresh();
    }

    /**
     * 后退到上一页
     */
    public void goBack() {
        driver.navigate().back();
    }

    /**
     * 前进到下一页
     */
    public void goForward() {
        driver.navigate().forward();
    }

    /**
     * 获取当前页面标题
     *
     * @return 页面标题
     */
    public String getPageTitle() {
        return driver.getTitle();
    }

    /**
     * 获取当前页面URL
     *
     * @return 当前URL
     */
    public String getCurrentUrl() {
        return driver.getCurrentUrl();
    }

    // ======================== 元素定位与操作 ========================

    /**
     * 通用等待方法
     *
     * @param condition 等待条件
     * @param <T>       等待结果类型
     * @return 等待结果
     */
    public <T> T waitForCondition(ExpectedCondition<T> condition) {
        return wait.until((Function<? super WebDriver, T>) condition);

    }

    /**
     * 等待元素可见并返回
     *
     * @param by 定位方式
     * @return WebElement 找到的元素
     */
    public WebElement waitForElementVisible(By by) {
        return waitForCondition(ExpectedConditions.visibilityOfElementLocated(by));
    }
    // 如果有多个相同ID的元素，等待至少一个存在
    public boolean waitForAnyElementPresent(By by) {
        return waitForCondition(driver -> !driver.findElements(by).isEmpty());
    }

    /**
     * 等待元素可见并返回（重载：直接传入元素）
     *
     * @param element 目标元素
     * @return WebElement 可见的元素
     */
    public WebElement waitForElementVisible(WebElement element) {
        return waitForCondition(ExpectedConditions.visibilityOf(element));
    }

    /**
     * 等待元素可点击并返回
     *
     * @param by 定位方式
     * @return WebElement 可点击的元素
     */
    public WebElement waitForElementClickable(By by) {
        return waitForCondition(ExpectedConditions.elementToBeClickable(by));
    }

    /**
     * 等待元素可点击并返回（重载：直接传入元素）
     *
     * @param element 目标元素
     * @return WebElement 可点击的元素
     */
    public WebElement waitForElementClickable(WebElement element) {
        return waitForCondition(ExpectedConditions.elementToBeClickable(element));
    }

    /**
     * 查找单个元素
     *
     * @param by 定位方式
     * @return WebElement 找到的元素
     */
    public WebElement findElement(By by) {
        return driver.findElement(by);
    }

    /**
     * 查找多个元素
     *
     * @param by 定位方式
     * @return 元素列表
     */
    public List<WebElement> findElements(By by) {
        return driver.findElements(by);
    }

    /**
     * 点击元素
     *
     * @param by 定位方式
     */
    public void clickElement(By by) {
        waitForElementClickable(by).click();
    }

    /**
     * 点击元素（重载：直接传入元素）
     *
     * @param element 目标元素
     */
    public void clickElement(WebElement element) {
        waitForElementClickable(element).click();
    }

    /**
     * 在元素中输入文本
     *
     * @param by   定位方式
     * @param text 要输入的文本（可为null，null视为清空）
     */
    public void inputText(By by, String text) {
        inputText(waitForElementVisible(by), text);
    }

    /**
     * 在元素中输入文本（重载：直接传入元素）
     *
     * @param element 目标元素
     * @param text    要输入的文本（可为null，null视为清空）
     */
    public void inputText(WebElement element, String text) {
        WebElement visibleElement = waitForElementVisible(element);
        visibleElement.clear();
        if (text != null) {
            visibleElement.sendKeys(text);
        }
    }

    /**
     * 获取元素文本
     *
     * @param by 定位方式
     * @return 元素文本
     */
    public String getElementText(By by) {
        return waitForElementVisible(by).getText();
    }

    /**
     * 获取元素文本（重载：直接传入元素）
     *
     * @param element 目标元素
     * @return 元素文本
     */
    public String getElementText(WebElement element) {
        return waitForElementVisible(element).getText();
    }

    /**
     * 获取元素属性值
     *
     * @param by            定位方式
     * @param attributeName 属性名（非空）
     * @return 属性值
     * @throws IllegalArgumentException 当attributeName为null/空时抛出
     */
    public String getElementAttribute(By by, String attributeName) {
        if (attributeName == null || attributeName.trim().isEmpty()) {
            throw new IllegalArgumentException("属性名不能为空");
        }
        return waitForElementVisible(by).getAttribute(attributeName);
    }

    /**
     * 获取元素属性值（重载：直接传入元素）
     *
     * @param element       目标元素
     * @param attributeName 属性名（非空）
     * @return 属性值
     */
    public String getElementAttribute(WebElement element, String attributeName) {
        if (attributeName == null || attributeName.trim().isEmpty()) {
            throw new IllegalArgumentException("属性名不能为空");
        }
        return waitForElementVisible(element).getAttribute(attributeName);
    }

    /**
     * 执行鼠标悬停操作
     *
     * @param by 定位方式
     */
    public void hoverOverElement(By by) {
        hoverOverElement(waitForElementVisible(by));
    }

    /**
     * 执行鼠标悬停操作（重载：直接传入元素）
     *
     * @param element 目标元素
     */
    public void hoverOverElement(WebElement element) {
        actions.moveToElement(waitForElementVisible(element)).perform();
    }

    /**
     * 执行右键点击操作
     *
     * @param by 定位方式
     */
    public void rightClickElement(By by) {
        rightClickElement(waitForElementVisible(by));
    }

    /**
     * 执行右键点击操作（重载：直接传入元素）
     *
     * @param element 目标元素
     */
    public void rightClickElement(WebElement element) {
        actions.contextClick(waitForElementVisible(element)).perform();
    }

    // ======================== 表单操作 ========================

    /**
     * 选择下拉框选项（通过可见文本）
     *
     * @param selectBy   下拉框定位方式
     * @param optionText 选项文本（非空）
     * @throws IllegalArgumentException 当optionText为null/空时抛出
     */
    public void selectDropdownOption(By selectBy, String optionText) {
        if (optionText == null || optionText.trim().isEmpty()) {
            throw new IllegalArgumentException("选项文本不能为空");
        }
        By optionBy = By.xpath(".//option[normalize-space(text())='" + optionText + "']");
        clickElement(selectBy);
        clickElement(optionBy);
    }

    /**
     * 提交表单
     *
     * @param by 表单定位方式
     */
    public void submitForm(By by) {
        waitForElementVisible(by).submit();
    }

    // ======================== JavaScript操作 ========================

    /**
     * 执行JavaScript代码
     *
     * @param script 要执行的JS代码（非空）
     * @param args   传递给JS代码的参数
     * @return 执行结果
     * @throws IllegalArgumentException 当script为null/空时抛出
     */
    public Object executeScript(String script, Object... args) {
        if (script == null || script.trim().isEmpty()) {
            throw new IllegalArgumentException("JS脚本不能为空");
        }
        JavascriptExecutor js = (JavascriptExecutor) driver;
        return js.executeScript(script, args);
    }

    /**
     * 滚动到元素位置
     *
     * @param by 定位方式
     */
    public void scrollToElement(By by) {
        scrollToElement(waitForElementVisible(by));
    }

    /**
     * 滚动到元素位置（重载：直接传入元素）
     *
     * @param element 目标元素
     */
    public void scrollToElement(WebElement element) {
        executeScript("arguments[0].scrollIntoView({block: 'center'});", waitForElementVisible(element));
    }

    // ======================== 窗口和框架操作 ========================

    /**
     * 切换到指定索引的窗口
     *
     * @param index 窗口索引（从0开始）
     * @throws IndexOutOfBoundsException 当索引超出窗口数量范围时抛出
     */
    public void switchToWindow(int index) {
        List<String> windowHandles = driver.getWindowHandles().stream()
                .collect(Collectors.toList());

        if (index < 0 || index >= windowHandles.size()) {
            throw new IndexOutOfBoundsException(
                    String.format("窗口索引无效: %d, 现有窗口数量: %d", index, windowHandles.size())
            );
        }
        driver.switchTo().window(windowHandles.get(index));
    }

    /**
     * 切换到iframe
     *
     * @param by 定位方式
     */
    public void switchToIframe(By by) {
        WebElement iframe = waitForElementVisible(by);
        driver.switchTo().frame(iframe);
    }

    /**
     * 切换到iframe（重载：直接传入元素）
     *
     * @param iframe 目标iframe元素
     */
    public void switchToIframe(WebElement iframe) {
        driver.switchTo().frame(waitForElementVisible(iframe));
    }

    /**
     * 切换回主文档
     */
    public void switchToDefaultContent() {
        driver.switchTo().defaultContent();
    }

    // ======================== 截图操作 ========================

    /**
     * 截取当前页面并保存（使用默认目录）
     *
     * @return 截图文件路径
     * @throws IOException 保存文件时发生错误
     */
    public String takeScreenshot() throws IOException {
        return takeScreenshot(DEFAULT_SCREENSHOT_DIR);
    }

    /**
     * 截取当前页面并保存
     *
     * @param directory 保存目录（为null时使用默认目录）
     * @return 截图文件路径
     * @throws IOException 保存文件时发生错误
     */
    public String takeScreenshot(String directory) throws IOException {
        // 处理目录为null的情况
        String targetDir = directory == null ? DEFAULT_SCREENSHOT_DIR : directory;

        // 创建目录（如果不存在）
        Files.createDirectories(Paths.get(targetDir));

        // 生成带时间戳的文件名（避免重复）
        String timestamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
        String filename = "screenshot_" + timestamp + ".png";
        String filePath = targetDir + File.separator + filename;

        // 截取并保存截图
        TakesScreenshot screenshot = (TakesScreenshot) driver;
        File sourceFile = screenshot.getScreenshotAs(OutputType.FILE);
        Files.copy(sourceFile.toPath(), Paths.get(filePath));

        return filePath;
    }

    // ======================== 工具方法 ========================

    /**
     * 等待指定时间（毫秒）
     *
     * @param milliseconds 毫秒数（>=0）
     * @throws IllegalArgumentException 当毫秒数为负数时抛出
     */
    public void waitMillis(long milliseconds) {
        if (milliseconds < 0) {
            throw new IllegalArgumentException("等待时间不能为负数");
        }
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保留中断状态
            throw new RuntimeException("等待被中断", e);
        }
    }

    /**
     * 检查元素是否存在
     *
     * @param by 定位方式
     * @return 如果存在返回true，否则返回false
     */
    public boolean isElementPresent(By by) {
        try {
            driver.findElement(by);
            return true;
        } catch (NoSuchElementException e) {
            return false;
        }
    }

    /**
     * 关闭浏览器
     */
    public void quitBrowser() {
        if (driver != null) {
            driver.quit();
        }
    }

    /**
     * 获取WebDriver实例
     *
     * @return WebDriver实例
     */
    public WebDriver getDriver() {
        return driver;
    }

    /**
     * 获取当前超时时间（秒）
     */
    public int getTimeoutSeconds() {
        return timeoutSeconds;
    }

    /**
     * 设置超时时间（动态调整）
     *
     * @param timeoutSeconds 新的超时时间（秒，必须>0）
     */
    public void setTimeoutSeconds(int timeoutSeconds) {
        if (timeoutSeconds <= 0) {
            throw new IllegalArgumentException("超时时间必须大于0");
        }
        this.timeoutSeconds = timeoutSeconds;
        this.wait = new WebDriverWait(driver, (long) timeoutSeconds * 1000);

    }

    // ======================== 页面加载等待 ========================

    /**
     * 等待页面完全加载完成（document.readyState 变为 complete）
     * 等待页面的 HTML 文档加载完成并解析完毕，所有资源（图片、样式等）加载完成
     */
    public void waitForPageLoad() {
        waitForCondition(driver -> {
            // 执行JS获取文档就绪状态
            String readyState = (String) ((JavascriptExecutor) driver).executeScript("return document.readyState");
            // 就绪状态为complete表示页面完全加载完成
            return "complete".equals(readyState);
        });
    }
    // ======================== 页面加载与刷新控制 ========================

    /**
     * 等待页面加载完成，若5秒内未完成则刷新页面，最多尝试3次
     *
     * @return 若成功加载完成返回true，3次尝试后仍失败返回false
     */
    public boolean waitForPageLoadWithRefresh() {
        // 保存原始超时时间，用于方法结束后恢复
        int originalTimeout = this.timeoutSeconds;
        // 最多尝试3次
        int maxAttempts = 3;

        try {
            // 临时将超时时间设置为5秒（单次等待时长）
            setTimeoutSeconds(5);

            for (int attempt = 1; attempt <= maxAttempts; attempt++) {
                try {
                    // 等待页面加载完成（5秒超时）
                    waitForCondition(driver -> {
                        String readyState = (String) ((JavascriptExecutor) driver).executeScript("return document.readyState");
                        return "complete".equals(readyState);
                    });
                    // 加载成功，返回true
                    return true;
                } catch (TimeoutException e) {
                    // 第5秒未加载完成，若未到最大尝试次数则刷新页面
                    if (attempt < maxAttempts) {
                        refreshPage();
                        // 刷新后短暂等待，避免频繁操作
                        waitMillis(500);
                    }
                }
            }
            // 3次尝试后仍未成功
            return false;
        } finally {
            // 恢复原始超时时间，避免影响其他方法
            setTimeoutSeconds(originalTimeout);
        }
    }

    /**
     * 获取当前窗口的句柄
     *
     * @return 当前窗口句柄字符串
     */
    public String getCurrentWindowHandle() {
        return driver.getWindowHandle();
    }

    /**
     * 通过窗口句柄切换到指定窗口
     *
     * @param handle 目标窗口句柄
     */
    public void switchToWindow(String handle) {
        driver.switchTo().window(handle);
    }

    /**
     * 获取所有窗口句柄
     *
     * @return 窗口句柄列表
     */
    public Set<String> getAllWindowHandles() {
        return driver.getWindowHandles();
    }

    /**
     * 关闭所有标签
     */

    public boolean closeAllTabs() {
        try {
            // 获取当前窗口句柄
            String originalWindow = driver.getWindowHandle();

            // 切换到所有窗口并关闭，除了原始窗口
            for (String windowHandle : driver.getWindowHandles()) {
                if (!originalWindow.equals(windowHandle)) {
                    driver.switchTo().window(windowHandle);
                    driver.close();
                }
            }

            // 切换回原始窗口
            driver.switchTo().window(originalWindow);

            // 可选：如果想关闭最后一个标签页，可以使用下面的JavaScript
            // ((JavascriptExecutor) driver).executeScript("window.close();");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 检查父元素内是否存在指定子元素
     *
     * @param by     子元素定位方式
     * @param parent 父元素
     * @return 存在返回true，否则返回false
     */
    public boolean isElementPresent(By by, WebElement parent) {
        try {
            parent.findElement(by);
            return true;
        } catch (NoSuchElementException e) {
            return false;
        }
    }

    /**
     * 在父元素内查找子元素并等待其可见
     *
     * @param by     子元素定位方式
     * @param parent 父元素
     * @return 可见的子元素
     */
    public WebElement waitForElementVisible(By by, WebElement parent) {
        return waitForCondition(ExpectedConditions.visibilityOf(parent.findElement(by)));
    }

    /**
     * 在父元素内点击子元素
     *
     * @param by     子元素定位方式
     * @param parent 父元素
     */
    public void clickElement(By by, WebElement parent) {
        waitForElementClickable(parent.findElement(by)).click();
    }

}